package net.peelmeagrape.hibernate;

import net.peelmeagrape.hibernate.xmlgen.*;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;

/**
 * Declares a persistent, JavaBean style property of the class.
 * Generates a hibernate &lt;property&gt; mapping element
 * (<a href="{@docRoot}/reference/en/html/mapping.html#mapping-declaration-property">Hibernate Documentation for &lt;property&gt;</a>).
 * <p>Derived properties can be declared using {@link #formula()}.</p>
 * <p>Properties can be grouped together using {@link #naturalId()}, {@link #join()} or
 * {@link #properties()}</p>
 */
@Target({METHOD,FIELD}) @Retention(RUNTIME)
@XmlElement(name="property")
@AddPropertyNameAsAttribute(name="name")
public @interface Property
{
    @ChildElement(0) Meta[] meta() default {};
    @XmlAttribute String node() default "";

    /**
     * The strategy Hibernate should use for accessing the property value. This may be one of
     * 'property', 'field', 'embedded', or the name of a subclass of {@link org.hibernate.property.PropertyAccessor}.
     */
    @XmlAttribute String access() default "property";

    /**
     * A name that indicates the Hibernate type. This could be one of:
     * <ol compact="compact" type="1">
     * <li>
     * The name of a Hibernate basic type (eg. <tt class="literal">integer, string, character,
     * date, timestamp, float, binary, serializable, object, blob</tt>).
     * </li>
     * <li>
     * The name of a Java class with a default basic type (eg. <tt class="literal">int, float,
     * char, java.lang.String, java.util.Date, java.lang.Integer, java.sql.Clob</tt>).
     * </li>
     * <li>
     * The name of a serializable Java class.
     * </li>
     * <li>
     * The class name of a custom type (eg. <tt class="literal">com.illflow.type.MyCustomType</tt>).
     * </li>
     * </ol>
     * If you do not specify a type, Hibernate will use reflection upon the named
     * property to take a guess at the correct Hibernate type. Hibernate will try to
     * interpret the name of the return class of the property getter using rules 2, 3,
     * 4 in that order. However, this is not always enough.
     * In certain cases you will still need the {@link #type()}
     * attribute. (For example, to distinguish between {@link org.hibernate.Hibernate#DATE}
     * and {@link org.hibernate.Hibernate#TIMESTAMP}, or to specify a custom type.)
     */
    @XmlAttribute String type() default "";

    /**
     * (optional - defaults to the property name): the name of the mapped database table column.
     * This may also be specified by nested {@link #column()} element(s).
     */
    @XmlAttribute String column() default "";
    @XmlAttribute int length() default 255;
    @XmlAttribute int precision() default -1;
    @XmlAttribute int scale() default -1;

    /**
     * (optional): Enable the DDL generation of a nullability constraint for the columns.
     */
    @XmlAttribute("not-null") boolean notNull() default false;

    /**
     * (optional): Enable the DDL generation of a unique constraint for the columns.
     * Also, allow this to be the target of a property-ref (e.g. {@link net.peelmeagrape.hibernate.ManyToOne#propertyRef()}).
     */
    @XmlAttribute boolean unique() default false;
    @XmlAttribute("unique-key") String uniqueKey() default "";

    /**
     * Specifies that the mapped columns should be included in SQL UPDATE statement.
     * Setting both {@link #update()} and {@link #insert()} to false
     * allows a pure "derived" property whose value is initialized from some
     * other property that maps to the same colum(s) or by a trigger or other application.
     */
    @XmlAttribute boolean update() default true;

    /**
     * Specifies that the mapped columns should be included in SQL INSERT statement.
     * Setting both {@link #update()} and {@link #insert()} to false
     * allows a pure "derived" property whose value is initialized from some
     * other property that maps to the same colum(s) or by a trigger or other application.
     */
    @XmlAttribute boolean insert() default true;

    /**
     * (optional - defaults to true): Specifies that updates to this property do or do not require acquisition of the
     * optimistic lock. In other words, determines if a version increment should occur when this property is dirty.
     */
    @XmlAttribute("optimistic-lock") boolean optimisticLock() default true;

    /**
     * An SQL expression that defines the value for a computed property. Computed properties do not have a column mapping of their own.
     * An especially powerful feature are derived properties. These properties are by definition read-only,
     * the property value is computed at load time. You declare the computation as a SQL expression, this
     * translates to a SELECT  clause subquery in the SQL query that loads an instance:
     * <pre>
     * formula="( SELECT SUM (li.quantity*p.price) FROM LineItem li, Product p
                WHERE li.productId = p.productId
                AND li.customerId = customerId
                AND li.orderNumber = orderNumber )"
      </pre>
     * Note that you can reference the entities own table by not declaring an alias on a particular column
     * (customerId in the given example).
     */
    @XmlAttribute String formula() default "";
    @XmlAttribute String index() default "";

    /**
     * (optional - defaults to false): Specifies that this property should be fetched lazily when the instance
     * variable is first accessed (requires build-time bytecode instrumentation).
     */
    @XmlAttribute boolean lazy() default false;
    @ChildElement(1) Column[] columnMapping() default {};
    @ChildElement(2) TypeMapping[] typeMapping() default {};

    /**
     * Specify that this property is part of the entities {@link NaturalId}.
     */
    @FindParentElement(name="natural-id") boolean naturalId() default false;

    /**
     * Specify that this property is stored in a joined table. See {@link Join}.
     */
    @FindParentElement(
        name="join",
        idAttributeName = "table",
        reorderNewParent = "property|sql-insert?,sql-update?,sql-delete?"
    )
        String[] join() default {};

    /**
     * Specify that this property is part of a properties grouping. See {@link Properties}.
     */
    @FindParentElement(
        name="properties",
        idAttributeName = "name"
    )
        String[] properties() default {};
}
